package world;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;


import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import world.edges.Door;
import world.placement.*;
import world.square.*;



/**
 * A class representing a Shaft (schacht).
 * 
 * @param 	<S>
 * 			The type parameter, which is a square.
 * 
 * @author Frederic Mes, Wim Marynissen
 * @version 1.0
 */
public class Shaft<S extends Square> extends Dungeon<S> {
	
	/**
	 * Creates a new shaft with the specified maximum dimensions.
	 * @param 	direction
	 * 		  	The direction in which this shaft is variable.
	 * @param 	size
	 * 		  	The size of the shaft.
	 * 
     * @effect 	This new shaft is initialized as a dungeon with standard maximum dimensions. These maximum dimensions will be expanded.
	 *          | super(0, 0, 0)
	 * @post	The fixed position is set equal to the origin.
	 * 			| new.getFixedPosition() == new Position(0, 0, 0)
	 * @throws	IllegalArgumentException
	 * 			The provided size must be valid.
	 * 			| !isValidSize(size)
	 */

	public Shaft (Direction direction, long size) throws IllegalArgumentException{
		super (0, 0, 0);
		if(!isValidSize(size))
			throw new IllegalArgumentException("Invalid shaft size!");
		this.size = size;
		shaftDirection = direction;
		if (getShaftDirection().isDirectionOrOppositeOf(Direction.NORTH)){
			this.setMaxYCoord(size);
		}else if (getShaftDirection().isDirectionOrOppositeOf(Direction.EAST)){
			this.setMaxXCoord(size);
		}else{
			this.setMaxZCoord(size);
		}
		setFixedPosition(new Position(0, 0, 0));
	}
		
	/**
	 * Returns the size of the shaft
	 */
	@Immutable @Basic 
	public long getSize(){
		return size;
	}
	
	/**
	 * Variable containing the size of the shaft.
	 */
	public final long size;

	/**
	 * Returns whether or not this shaft can have a given square as its new square.
	 * @param 	square
	 * 		  	The square to be checked.
	 * @return 	False if the square is a rock
	 * 		   	| if (square instanceof Rock)
	 * 		   	|	then result == false
	 * @return 	False if super cannot have the square.
	 * 		   	| result == super.canHaveAsSquare(square))
	 */
	@Override
	public boolean canHaveAsSquare(S square) { 
		return (super.canHaveAsSquare(square) && !(square instanceof Rock));
	}
	
	/**
	 * @param 	square
	 * 			The square to validate.
	 * @param 	position
	 * 		 	The position to be validated.
	 * @return	The square must be permitted to be added to this dungeon
	 * 			| if(!super.canHaveSquareAtPosition(square, position))
	 * 			|	then result == false
	 * @return 	False if there's a door in between of two squares in the same shaft.
	 * 		   	| if (square.getBorder(getShaftDirection()) != null)
 	 *		   	|	then if ((square.getBorder(getShaftDirection()).getBorderElement() instanceof Door)
 	 *			|				&& !isEndPosition(position))
 	 *		   	|		then result == false
 	 *	       	| if (square.getBorder(getShaftDirection().getOpposite()) != null)
	 *		   	|	then if ((square.getBorder(getShaftDirection().getOpposite()).getBorderElement() instanceof Door)
 	 *			|				&& !isStartPosition(position))
	 *		   	|		then result == false
	 * 
	 */
	public boolean canHaveSquareAtPosition(S square,Position position){ 
		if(!super.canHaveSquareAtPosition(square, position))
			return false;
		if (square.getBorder(getShaftDirection()) != null)
			if (square.getBorder(getShaftDirection()).getBorderElement() instanceof Door)
				 if (!isEndPosition(position))
					 return false;
		if (square.getBorder(getShaftDirection().getOpposite()) != null)
			if (square.getBorder(getShaftDirection().getOpposite())
					.getBorderElement() instanceof Door)
				 if (!isStartPosition(position))
					 return false;
		return true;
	}
	
   /**
    * Check if a value is a valid size for a shaft.
	* @param	size
	* 			The size to validate
	* @return	The size of the shaft can't be a negative value.
	* 			| result == (size >= 0)
	*/
	public boolean isValidSize(long size){
		return (size >= 0);
	}
	
	
	/**
	 * Check if a position is the starting position for this shaft.
	 * @param 	position
	 * 			The position to check.
	 * @return	True if the position is the origin of the coordinate system.
	 * 			| result == position.equals(new Position (0, 0, 0))
	 */
	public boolean isStartPosition (Position position){
		return position.equals(new Position (0, 0, 0));
	}
	

	/**
	 * Check if a position is the last position for this shaft.
	 * @param 	position
	 * 			The position to check.
	 * @return	True if the position is the maximum position.
	 */
	public boolean isEndPosition(Position position){
		if(getShaftDirection().isDirectionOrOppositeOf(Direction.NORTH))
			return position.equals(new Position (0, size, 0));
		if(getShaftDirection().isDirectionOrOppositeOf(Direction.EAST))
			return (position.equals(new Position (size, 0, 0)));
		return position.equals(new Position (0, 0, size));
	}

	/**
	 * Returns whether or not this shaft can have a given position as its new position.
	 * @param 	position
	 * 		  	The position to be checked.
	 * @return 	False if the direction of the shaft is east (along the x-axis) and the y- and z-coordinate are not 0 and the size is bigger than the maximum x coordinate.
	 * 		 	| if (getShaftDirection() == Direction.EAST){
	 *			|	then if (position.getYCoord() != 0 && position.getZCoord() != 0)
	 *			|		 then result ==  false
	 * @return 	False if the direction of the shaft is north (along the y-axis) and the x- and z-coordinate are not 0 and the size is bigger than the maximum y coordinate..
	 * 			| if (getShaftDirection() == Direction.NORTH){
	 *			|  	then if (position.getXCoord() != 0 && position.getZCoord() != 0)
	 *			|		then result == false
	 * @return  False if the direction of the shaft is anything else (along the z-axis) and the x- and y-coordinate are not 0 and the size is bigger than the maximum z coordinate..
	 * 			| if (position.getXCoord() != 0 && position.getYCoord() != 0)
	 *			|	 then result == false
	 * @return  The position cannot already contain a square.
	 * 			|for each positionInDungeon in getSquares()
	 * 			| 	if (position.equals(positionInDungeon))
	 * 			|		then result == false
	 */
	@Override
	public boolean canHaveAsPosition(Position position) {
		
		if (getShaftDirection().isDirectionOrOppositeOf(Direction.EAST)){
			if (position.getYCoord() != 0 && position.getZCoord() != 0 && getSize() <= getMaxXCoord())
				return false;
		}
		else if (getShaftDirection().isDirectionOrOppositeOf(Direction.NORTH)){
			if (position.getXCoord() != 0 && position.getZCoord() != 0 && getSize() <= getMaxYCoord())
				return false;
		}
		else{
			if (position.getXCoord() != 0 && position.getYCoord() != 0 && getSize() <= getMaxZCoord())
				return false;
		}
		
		for (Position positionInDungeon : getSquares().values()) {
			if (position.equals(positionInDungeon))
				return false;
		}
		return true;
		
	}
	
	/**
	 * Returns the direction of the shaft.
	 */
	@Basic @Immutable 
	public Direction getShaftDirection (){
		return shaftDirection;
	}
	
	/**
	 * Variable containing the direction of the shaft.
	 */
	private Direction shaftDirection;

	/**
	 * Returns the dungeon which contains this dungeon
	 */
	@Override @Basic
	public Composite<S> getContainingDungeon() {
		return containingDungeon;
	}

	/**
	 * Returns the rootdungeon of this dungeon.
	 * 
	 * @return The rootdungeon is this if it has no containing dungeon.
	 * 		   |if (getContainingDungeon() == null)
	 * 		   |	then result == this
	 * @return The rootdungeon is the rootdungeon of this containing dungeon of this.
	 * 		   | result == getContainingDungeon().getRootDungeon()
	 */
	@Override
	public Dungeon<S> getRootDungeon() {
		if (getContainingDungeon() == null)
			return this;
		return getContainingDungeon().getRootDungeon();
	}

	/**
	 * Sets the the containing dungeon to the given value.
	 */
	@Override
	public void setContainingDungeon(Composite<S> dungeon) {
		this.containingDungeon = dungeon;		
	}	
	
	/**
	 * Variable registering the containing dungeon. Standard value is null.
	 */
	private Composite<S> containingDungeon;

	/**
	 * Return an iterator returning all the elements in this 
	 * level dungeon.
	 */
	@Override
	public Iterator<S> iterator() {
		return new Iterator<S>() {

			private Iterator<S> currentIterator = getSquares().keySet().iterator();
			

			public boolean hasNext() {
				return currentIterator.hasNext();
			}

			public S next() throws NoSuchElementException {
				if (!hasNext())
					throw new NoSuchElementException();
				return currentIterator.next();
			}

			public void remove() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}
		};
	}


	/**
	 * A method returning all the possible positions that can exist in this shaft.
	 * 
	 * @return	An arraylist of positions. 
	 * 			| let 
	 * 			|	i = 0
	 * 			| in
	 * 			| 	if (getShaftDirection() == Direction.EAST)
	 * 		    |		then for i <= getSize(),i++:
	 *  		| 			result.contains (new Position (i, 0, 0))
	 *  		| 	if (getShaftDirection() == Direction.EAST)
	 * 		    |		then for i <= getSize(),i++:
	 *  		| 			result.contains (new Position (i, 0, 0))
	 *  		| 	else
	 *  		| 		then for i <= getSize(),i++: 
	 *  		|			result.contains (new Position(0, 0, i)	
	 */
	@Override
	public ArrayList<Position> getAllPossiblePositions() {
		ArrayList<Position> positions = new ArrayList<Position>();

		if (getShaftDirection().isDirectionOrOppositeOf(Direction.EAST)) {
			for (long i = 0; i <= getSize(); i++) {
				positions.add(new Position(i, 0, 0));
			}
		} else if (getShaftDirection().isDirectionOrOppositeOf(Direction.NORTH)) {
			for (long i = 0; i <= getSize(); i++) {
				positions.add(new Position(0, i, 0));
			}
		} else {
			for (long i = 0; i <= getSize(); i++) {
				positions.add(new Position(0, 0, i));
			}
		}
		return positions;
	}
	/**
	 * @param 	maxXCoord
	 * 			The maximum size this dungeon will have in the x-direction
	 * @throws	UnsupportedOperationException
	 * 			The maximum x coordinate of a level can't be changed.
	 * @effect  If possible, set the maximum x coordinate as for any dungeon.
	 * 			| super.setMaxXCoord(maxXCoord)
	 */
    @Override
    public void setMaxXCoord(long maxXCoord) throws UnsupportedOperationException{
    	if(!getShaftDirection().isDirectionOrOppositeOf(Direction.EAST))
    		throw new UnsupportedOperationException("The x coordinate of a this shaft can't change.");
    	super.setMaxXCoord(maxXCoord);
    }
	/**
	 * @param 	maxYCoord
	 * 			The maximum size this dungeon will have in the y-direction
	 * @throws	UnsupportedOperationException
	 * 			The maximum y coordinate of a level can't be changed.
	 * @effect  If possible, set the maximum y coordinate as for any dungeon.
	 * 			| super.setMaxYCoord(maxYCoord)
	 */
    @Override
    public void setMaxYCoord(long maxYCoord) throws UnsupportedOperationException{
    	if(!getShaftDirection().isDirectionOrOppositeOf(Direction.NORTH))
    		throw new UnsupportedOperationException("The y coordinate of a this shaft can't change.");
    	super.setMaxYCoord(maxYCoord);
    }
	/**
	 * @param 	maxZCoord
	 * 			The maximum size this dungeon will have in the z-direction
	 * @throws	UnsupportedOperationException
	 * 			The maximum z coordinate of a level can't be changed.
	 * @effect  If possible, set the maximum z coordinate as for any dungeon.
	 * 			| super.setMaxZCoord(maxZCoord)
	 */
    @Override
    public void setMaxZCoord(long maxZCoord) throws UnsupportedOperationException{
    	if(!getShaftDirection().isDirectionOrOppositeOf(Direction.CEILING))
    		throw new UnsupportedOperationException("The z coordinate of a this shaft can't change.");
    	super.setMaxZCoord(maxZCoord);
    }
   
}
